﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.Objects.DataClasses;
using System.Linq;
using System.Text;
using Fintech.Utils;
using ZOVNP.Model.Abstracts;
using EntityState = ZOVNP.Model.Abstracts.EntityState;

namespace ZOVNP.Engine
{
    /// <summary> Базовый класс для всех доменных объектов. </summary>
    /// <remarks> Обходит некоторые ограничения кодогенерации. </remarks>
    public abstract class DomainObject : EntityObject, IDomainObject, ITypeCodeProvider
    {
        #region Поля

        /// <summary> Контекст, к которому в данный момент отнесен доменный объект. </summary>
        private ZOVNPContext context;
        /// <summary> Расширенные свойства объекта. </summary>
        private Dictionary<string, object> extendedProperties;

        #endregion

        #region Конструкторы

        /// <summary> Конструктор по умолчанию. </summary>
        public DomainObject() : base() { }

        #endregion

        #region Свойства

        /// <summary> Получить идентификатор типа объекта. </summary>
        public string ObjectTypeCode
        {
            get
            {
                var attrs = GetType().GetCustomAttributes(typeof(DescriptionAttribute), true);
                if (attrs.Length > 0) return ((DescriptionAttribute)attrs[0]).Description;
                return GetType().Name;
            }
        }

        public bool New { get; internal set; }

        /// <summary> Получить текущее состояние доменного объекта. </summary>
        EntityState IDomainObject.EntityState { get { return (EntityState)EntityState; } }

        /// <summary> Явная реализация получения уникального ключа доменного объекта. </summary>
        Guid IKeyable.ID
        {
            get
            {
                var id = GetID();
                return id.Value;
                // такая реализация через рефлексию была раньше, потом я в кодогенератор к каждому доменному объекту добавил метод GetID()
                //if (id.HasValue) return id.Value;
                //return (Guid)GetType().GetProperty("ID", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance).GetValue(this, null);
            }
        }

        /// <summary> Контекст, к которому принадлежит данный доменный объект. </summary>
        internal ZOVNPContext Context
        {
            get { return context; }
            set
            {
                if (context != value)
                {
                    context = value;
                    OnContextChanged();
                }
            }
        }

        /// <summary> Контекст, к которому принадлежит данный доменный объект. </summary>
        IDomainContext IDomainObject.Context { get { return context; } }

        /// <summary> Расширенные присоединенные свойства объекта. </summary>
        /// <remarks> Эти свойства не участвуют в процессе обновления. </remarks>
        public Dictionary<string, object> ExtendedProperties
        {
            get { return extendedProperties; }
            protected internal set { extendedProperties = value; }
        }

        #endregion

        #region Методы

        /// <summary> Присоединить к объекту дополнительное свойство. </summary>
        /// <param name="PropertyName"> Имя свойства. </param>
        /// <param name="PropertyValue"> Значение свойства. </param>
        public void AddProperty(string PropertyName, object PropertyValue)
        {
            if (extendedProperties == null) extendedProperties = new Dictionary<string, object>();
            extendedProperties.Add(PropertyName, PropertyValue);
        }

        /// <summary> Получить присоединенное к объекту дополнительное свойство. </summary>
        /// <typeparam name="T"> Тип значения свойства. </typeparam>
        /// <param name="PropertyName"> Имя свойства. </param>
        /// <returns> Значение свойства. </returns>
        public T GetProperty<T>(string PropertyName) { return (T)ExtendedProperties[PropertyName]; }

        /// <summary> Попытка получения присоединенного к объекту дополнительного свойства. </summary>
        /// <typeparam name="T"> Тип значения свойства. </typeparam>
        /// <param name="PropertyName"> Имя свойства. </param>
        /// <param name="PropertyValue"> Значение свойства. </param>
        /// <returns> true, если требуемое свойство обнаружено, иначе false. </returns>
        public bool TryGetProperty<T>(string PropertyName, out T PropertyValue)
        {
            if (extendedProperties == null)
            {
                PropertyValue = default(T);
                return false;
            }
            object value;
            var result = ExtendedProperties.TryGetValue(PropertyName, out value);
            PropertyValue = result ? (T)value : default(T);
            return result;
        }

        /// <summary> Получить присоединенное к объекту дополнительное булево свойство. </summary>
        /// <param name="PropertyName"> Имя свойства. </param>
        /// <returns> Значение свойства. </returns>
        public bool GetBoolProperty(string PropertyName) { return (bool)ExtendedProperties[PropertyName]; }

        /// <summary> Получить присоединенное к объекту дополнительное строковое свойство. </summary>
        /// <param name="PropertyName"> Имя свойства. </param>
        /// <returns> Значение свойства. </returns>
        public string GetStringProperty(string PropertyName) { return (string)ExtendedProperties[PropertyName]; }

        /// <summary> Получить присоединенное к объекту дополнительное целочисленное свойство. </summary>
        /// <param name="PropertyName"> Имя свойства. </param>
        /// <returns> Значение свойства.  </returns>
        public int GetIntProperty(string PropertyName) { return (int)ExtendedProperties[PropertyName]; }

        /// <summary> Проверка наличия у объекта контекста, в рамках которого идет взаимодействие с хранилищем. </summary>
        /// <remarks> Если контекст не задан, то будет сгенерировано исключение. </remarks>
        protected internal void GuardContextNeeded()
        {
            if (context == null)
                throw new InvalidOperationException(Properties.Resources.MSG_NeedContext);
            if (context.IsDisposed)
                throw new ObjectDisposedException(Properties.Resources.MSG_ContextDisposed);
        }

        /// <summary> Ловушка изменения принадлежности объекта к контексту. </summary>
        protected virtual void OnContextChanged()
        {
            var casted = this as ICacheableDomainObject;
            if (casted != null && Context != null) // сброс кеша происходит при изменении контекста на новый
                casted.RefreshAdditionalCache();
        }

        /// <summary> Получить уникальный ключ доменного объекта. </summary>
        /// <returns> Уникальный идентификатор. </returns>
        /// <remarks> Этот метод следует переопределить в наследниках (в генераторе кода), чтобы оптимизировать вызов свойства <c>IKeyable.ID</c>. </remarks>
        protected virtual Guid? GetID() { return null; }

        /// <summary> Получить хеш-код объекта. </summary>
        /// <returns> Хеш-код. </returns>
        public override int GetHashCode() { return ((IKeyable)this).ID.GetHashCode(); }

        /// <summary> Проверка эквивалентности данного объекта с другим объектом. </summary>
        /// <param name="obj"> Сравниваемый объект. </param>
        /// <returns> True, если объекты эквивалентны, иначе false. </returns>
        public override bool Equals(object obj)
        {
            var casted = obj as DomainObject;
            if (object.ReferenceEquals(casted, null)) return false;
            if (object.ReferenceEquals(casted, this)) return true;
            return ((IKeyable)casted).ID == ((IKeyable)this).ID;
        }

        /// <summary> Получение информации о максимальной длине строковых полей и полей-массивов для данного объекта. </summary>
        /// <param name="PropertyName"> Имя свойства, максимальную длину которого нужно получить. </param>
        /// <returns> Максимальная длина либо -1, если она не ограничена. </returns>
        public int MaxLength(string PropertyName)
        {
            GuardContextNeeded();
            return Context.Engine.Metadata.MaxLength(GetType(), PropertyName);
        }

        public virtual bool CheckSelfProperties()
        {
            return true;
        }

        #endregion
    }
}
